package com.thunder.ktv.scoreengine2test.ui;// SpectrumView.java
// SpectrumView.java

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Choreographer;
import android.view.View;

import androidx.annotation.Nullable;

import java.util.LinkedList;

public class SpectrumView extends View implements Choreographer.FrameCallback {
    private static final String TAG = "SpectrumViewChoreo";

    private Paint spectrumPaint;
    private Paint bitmapPaint;

    private Bitmap offscreenBitmap;
    private Canvas offscreenCanvas;

    // --- Data and Configuration ---
    private float[] pendingSpectrumData; // 新数据，等待Choreographer处理
    private final Object dataLock = new Object(); // 用于同步访问pendingSpectrumData
    private boolean hasPendingData = false;

    private LinkedList<float[]> spectrumHistory;
    private int maxHistorySizeBasedOnWidth = 200;

    private int spectrumBarWidth = 2;
    private final int SPECTRUM_DATA_SIZE = 256; // 固定为256
    private int viewBackgroundColor = Color.BLACK;
    private int spectrumBaseColor = Color.GREEN;

    // --- Choreographer ---
    private Choreographer choreographer;
    private boolean frameCallbackRegistered = false;

    // --- Drawing Helpers ---
    private Rect srcRect = new Rect();
    private Rect dstRect = new Rect();


    public SpectrumView(Context context) {
        super(context);
        init();
    }

    public SpectrumView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SpectrumView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        spectrumPaint = new Paint();
        spectrumPaint.setStyle(Paint.Style.FILL);
        spectrumPaint.setAntiAlias(false);

        bitmapPaint = new Paint();
        bitmapPaint.setFilterBitmap(false); // No filtering for sharp pixels

        spectrumHistory = new LinkedList<>();
        choreographer = Choreographer.getInstance();
    }

    public void updateSpectrum(float[] newSpectrum) {
//        Log.d(TAG, "updateSpectrum: " + Arrays.toString(newSpectrum));
        float[] dataCopy;
        if (newSpectrum == null || newSpectrum.length != SPECTRUM_DATA_SIZE) {
            // Log.w(TAG, "Invalid spectrum data received. Using zeros.");
            dataCopy = new float[SPECTRUM_DATA_SIZE]; // Use a zero array
        } else {
            dataCopy = newSpectrum.clone(); // Always clone to avoid external modification
        }

        synchronized (dataLock) {
            pendingSpectrumData = dataCopy;
            hasPendingData = true;
        }

        // Request a frame callback if one isn't already pending
        if (!frameCallbackRegistered) {
            frameCallbackRegistered = true;
            choreographer.postFrameCallback(this);
        }
    }

    @Override
    public void doFrame(long frameTimeNanos) {
        frameCallbackRegistered = false; // Reset for next updateSpectrum call

        float[] dataToProcess = null;
        boolean processThisFrame = false;

        synchronized (dataLock) {
            if (hasPendingData) {
                dataToProcess = pendingSpectrumData;
                pendingSpectrumData = null; // Consume the data
                hasPendingData = false;
                processThisFrame = true;
            }
        }

        if (processThisFrame && dataToProcess != null) {
            if (getWidth() > 0 && getHeight() > 0) {
                if (ensureOffscreenBitmap()) {
                    updateBitmapWithData(dataToProcess);
                    invalidate(); // Redraw the view with the updated bitmap
                }
            }
        }

        // If there's still pending data (e.g., updateSpectrum called again before this frame processed),
        // or if you want continuous animation, re-register.
        // For this specific use case, usually one updateSpectrum triggers one doFrame.
        // If updateSpectrum is called very rapidly, hasPendingData might become true again
        // quickly. The next postFrameCallback will handle it.
    }

    private void updateBitmapWithData(float[] spectrumData) {
        // 1. Add to history
        spectrumHistory.addLast(spectrumData);
        trimSpectrumHistory();

        // 2. Shift bitmap content
        if (offscreenBitmap.getWidth() > spectrumBarWidth) {
            // Source: everything اکسپت the leftmost barWidth pixels
            srcRect.set(spectrumBarWidth, 0, offscreenBitmap.getWidth(), offscreenBitmap.getHeight());
            // Destination: the leftmost (getWidth - barWidth) pixels
            dstRect.set(0, 0, offscreenBitmap.getWidth() - spectrumBarWidth, offscreenBitmap.getHeight());
            offscreenCanvas.drawBitmap(offscreenBitmap, srcRect, dstRect, null); // null paint for fast copy
        } else {
            // Bitmap is too narrow to shift, or barWidth covers everything
            offscreenCanvas.drawColor(viewBackgroundColor); // Clear the narrow area
        }


        // 3. Draw the new spectrum bar on the rightmost part
        float newBarX = Math.max(0, offscreenBitmap.getWidth() - spectrumBarWidth);
        float actualBarWidthToDraw = offscreenBitmap.getWidth() - newBarX;

        // Clear the area for the new bar first (important if barWidth < actual space or for transparency)
        spectrumPaint.setColor(viewBackgroundColor);
        offscreenCanvas.drawRect(newBarX, 0, offscreenBitmap.getWidth(), offscreenBitmap.getHeight(), spectrumPaint);

        drawSpectrumBarOnCanvas(offscreenCanvas, spectrumData, newBarX, actualBarWidthToDraw,
                offscreenBitmap.getHeight(), SPECTRUM_DATA_SIZE);
    }


    private boolean ensureOffscreenBitmap() {
        int width = getWidth();
        int height = getHeight();
        if (width <= 0 || height <= 0) {
            releaseOffscreenBitmap();
            return false;
        }

        if (offscreenBitmap == null || offscreenBitmap.getWidth() != width || offscreenBitmap.getHeight() != height) {
            releaseOffscreenBitmap();
            try {
                // Consider Bitmap.Config.RGB_565 for memory saving if alpha is not needed
                offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                offscreenCanvas = new Canvas(offscreenBitmap);
                Log.d(TAG, "Created offscreen bitmap: " + width + "x" + height);
                redrawEntireBitmapFromHistory(); // Fill the new bitmap
            } catch (OutOfMemoryError e) {
                Log.e(TAG, "OutOfMemoryError creating offscreen bitmap", e);
                offscreenBitmap = null;
                offscreenCanvas = null;
                return false;
            }
        }
        return true;
    }

    private void redrawEntireBitmapFromHistory() {
        if (offscreenBitmap == null || offscreenCanvas == null || getWidth() <=0 || getHeight() <=0) return;

        offscreenCanvas.drawColor(viewBackgroundColor);

        int viewWidth = offscreenBitmap.getWidth();
        int viewHeight = offscreenBitmap.getHeight();
        float currentBarWidth = this.spectrumBarWidth;

        int drawableHistoryCount = Math.min(spectrumHistory.size(), (int) (viewWidth / currentBarWidth));
        if (drawableHistoryCount <= 0) return;

        for (int i = 0; i < drawableHistoryCount; i++) {
            int historyIndex = spectrumHistory.size() - drawableHistoryCount + i;
            if (historyIndex < 0 || historyIndex >= spectrumHistory.size()) continue; // Safety check

            float[] spectrumData = spectrumHistory.get(historyIndex);
            float x = viewWidth - (drawableHistoryCount - i) * currentBarWidth;
            if (x + currentBarWidth <=0) continue;

            drawSpectrumBarOnCanvas(offscreenCanvas, spectrumData, x, currentBarWidth, viewHeight, SPECTRUM_DATA_SIZE);
        }
    }

    private void drawSpectrumBarOnCanvas(Canvas canvas, float[] spectrumData, float x, float barWidth,
                                         int canvasHeight, int dataPoints) {
        if (spectrumData == null || spectrumData.length != dataPoints || barWidth <= 0) return;

        float pointHeight = (float) canvasHeight / dataPoints;

        for (int j = 0; j < dataPoints; j++) {
            int value = (int) spectrumData[j];
//            Log.d(TAG, "drawSpectrumBarOnCanvas: " + j + " | " + value);
            int energyLevel = Math.min(255, Math.max(0, value));
            spectrumPaint.setColor(Color.argb(energyLevel*5,
                    Color.red(spectrumBaseColor),
                    Color.green(spectrumBaseColor),
                    Color.blue(spectrumBaseColor)));

            float yTop = canvasHeight - (j + 1) * pointHeight;
            // float yBottom = canvasHeight - j * pointHeight; // Not needed for drawRect if height is 1 "point"
            // To ensure rects don't overlap or leave gaps due to float precision,
            // it's often better to calculate top and bottom for each.
            float yBottom = yTop + pointHeight;


            // Ensure we don't draw tiny rects less than 1px high, or adjust pointHeight calculation
            // For simplicity here, we assume pointHeight >= 1
            canvas.drawRect(x, yTop, x + barWidth, yBottom, spectrumPaint);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Log.d(TAG, "onSizeChanged: " + w + "x" + h);
        if (w > 0 && h > 0) {
            if (spectrumBarWidth > 0) {
                maxHistorySizeBasedOnWidth = w / spectrumBarWidth + 2; // +2 for buffer and to ensure full coverage
            } else {
                maxHistorySizeBasedOnWidth = w;
            }
            trimSpectrumHistory();
            // Bitmap recreation and redraw will be handled by ensureOffscreenBitmap and redrawEntireBitmapFromHistory
            // This will be called before the next draw usually, or ensureOffscreenBitmap will catch it.
            // Forcing a redraw of bitmap content here ensures it's correct for the new size.
            if (ensureOffscreenBitmap()) { // This will create/resize and call redrawEntireBitmapFromHistory if needed
                // redrawEntireBitmapFromHistory(); // ensureOffscreenBitmap calls it if bitmap is new/resized
            }
        } else {
            releaseOffscreenBitmap();
        }
        invalidate();
    }

    private void trimSpectrumHistory() {
        while (spectrumHistory.size() > maxHistorySizeBasedOnWidth) {
            spectrumHistory.removeFirst();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (offscreenBitmap == null || offscreenBitmap.isRecycled() || getWidth() <=0 || getHeight() <=0) {
            canvas.drawColor(viewBackgroundColor); // Draw background if no bitmap
            return;
        }
        canvas.drawBitmap(offscreenBitmap, 0, 0, bitmapPaint);
    }

    private void releaseOffscreenBitmap() {
        if (offscreenBitmap != null) {
            if (!offscreenBitmap.isRecycled()) {
                offscreenBitmap.recycle();
            }
            offscreenBitmap = null;
            offscreenCanvas = null;
            Log.d(TAG, "Offscreen bitmap released");
        }
    }

    // --- Lifecycle and Cleanup ---
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // If there was pending data when detached, re-register for a frame.
        // Or, simply wait for the next updateSpectrum call.
        // For simplicity, we don't automatically re-register here.
        // The next updateSpectrum will trigger it.
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (frameCallbackRegistered) {
            choreographer.removeFrameCallback(this);
            frameCallbackRegistered = false;
        }
        releaseOffscreenBitmap();
        // Clear any pending data to prevent leaks or stale processing if reattached
        synchronized (dataLock) {
            pendingSpectrumData = null;
            hasPendingData = false;
        }
        Log.d(TAG, "Detached from window, cleaned up.");
    }

    // --- Public Configuration Methods ---
    public void setSpectrumBarWidth(int widthInPx) {
        this.spectrumBarWidth = Math.max(1, widthInPx);
        if (getWidth() > 0) { // Only proceed if view has a size
            maxHistorySizeBasedOnWidth = getWidth() / this.spectrumBarWidth + 2;
            trimSpectrumHistory();
            if (ensureOffscreenBitmap()) {
                redrawEntireBitmapFromHistory();
            }
        }
        invalidate();
    }

    public void setSpectrumBaseColor(int color) {
        this.spectrumBaseColor = color;
        if (getWidth() > 0 && getHeight() > 0 && ensureOffscreenBitmap()) { // Ensure bitmap exists before redrawing
            redrawEntireBitmapFromHistory();
        }
        invalidate();
    }

    public void setSpectrumBackgroundColor(int color) {
        this.viewBackgroundColor = color;
        if (getWidth() > 0 && getHeight() > 0 && ensureOffscreenBitmap()) {
            redrawEntireBitmapFromHistory(); // Background color change requires full redraw of bitmap content
        }
        invalidate();
    }

    public void clear() {
        // Stop any pending frame callbacks
        if (frameCallbackRegistered) {
            choreographer.removeFrameCallback(this);
            frameCallbackRegistered = false;
        }
        synchronized (dataLock) {
            pendingSpectrumData = null;
            hasPendingData = false;
        }
        spectrumHistory.clear();
        if (offscreenCanvas != null) {
            offscreenCanvas.drawColor(viewBackgroundColor);
        }
        invalidate();
    }
}